#!/usr/bin/env python
#!encoding: utf-8

import os
import time
import urllib2
import subprocess
import signal

class ProcHandler:
    def __init__(self, cfgOpts):
        self.procDict, self.groupDict = cfgOpts
        self.procStatusList = []
        self.cdList = []

    def updateProcStatus(self):
        '''
        定时任务入口.

        function: 遍历配置文件中所有进程, 将它们的运行状态存储在self.procStatusList成员变量中;
        如发现有停止的进程, 则尝试重启它. 
        '''
        ## 遍历系统所有进程以查找配置文件中指定的进程
        for name in self.procDict.keys():
            procStatus = self.updateSpecProcStatus(name)
            ## 配置文件中列出的进程有未启动的, 尝试重启它
            if procStatus['pid'] == []:
                self.try2Restart(name)

    def updateSpecProcStatus(self, name):
        '''
        function: 更新指定名称name的进程的运行状态
        name: 字符串类型进程名称
        return: 返回指定进程更新后的运行状态
        '''
        proc = {
            'name': name,
            'status': 0,
            'pid': [],
            'msg': 'stopped'
        }
        psList = os.popen("ps -ef").read().splitlines()
        for psItem in psList:
            ## 目标字符串未找到指定子字符串时, 返回-1
            if psItem.find(self.procDict[name]['ps_str']) < 0:
                continue
            else:
                if proc['pid'] == []:
                    proc['status'] = 1
                    proc['pid'].append(int(psItem.split()[1]))
                    proc['msg'] = 'running'
                else:
                    proc['pid'].append(int(psItem.split()[1]))

        ## 将该进程最新状态"更新"到状态列表中
        self.updateProcStatusList(proc)
        return proc

    def updateProcStatusList(self, procStatus):
        '''
        function: 遍历self.procStatusList变量, 如果其中存在procStatus所表示的进程的运行状态, 则更新, 否则追加
        return: none
        '''
        for i in self.procStatusList:
            if i['name'] == procStatus['name']:
                self.procStatusList.remove(i)
                break
        self.procStatusList.append(procStatus)

    def try2Restart(self, name):
        '''
        function: 尝试重启目标名称为name的进程, 并更新其在进程状态列表中的信息.
        默认进程的auto_restart属性为True, 并且无冷却标志, 
        只有当显式设置auto_restart为False, 或者设置了冷却标识才不会自动重启
        name: 配置文件中procs键下的进程名, 字符串类型
        return: 返回目标名为name的进程状态
        '''
        if not (self.procDict[name].has_key('auto_restart') and self.procDict[name]['auto_restart'] == False):
            if not name in self.cdList:
                for i in range(0, 3):
                    print('try to restart: %d' % (i + 1))
                    self.startSpecProc(name)
                    time.sleep(3)
                    procInfo = self.updateSpecProcStatus(name)
                    ## 如果已经启动成功, 则直接退出
                    if not procInfo['pid'] == []:
                        return
                ## 指定次数内还是未启动成功, 就直接cd它, 不再尝试重启
                if procInfo['pid'] == []:
                    urllib2.urlopen('http://localhost:5000/process/mkcd?namelist=%s' % name)

    def getProcStatus(self, nameList):
        '''
        nameList: 待得到的进程名列表, 列表中的成员应该是配置文件中procs项中的成员
        '''
        statusList = []
        if 'all' in nameList:
            nameList = self.procDict.keys()
        for procName in nameList:
            procStatus = {}
            if not procName in self.procDict.keys():
                procStatus['name'] = procName
                procStatus['status'] = 0
                procStatus['msg'] = '配置文件中不存在此进程名, 请手动添加'
                statusList.append(procStatus)
            else:
                proc = self.updateSpecProcStatus(procName)
                if proc['name'] == procName:
                    statusList.append(proc)
        return statusList

    def getSpecProcStatus(self, name):
        '''
        获取以name为进程名的进程状态
        name为字符串类型, 本函数会为其构造列表类型
        '''
        nameList = []
        nameList.append(name)
        return self.getProcStatus(nameList)[0]

    def startProc(self, nameList):
        resultList = []
        if 'all' in nameList:
            nameList = self.procDict.keys()
        for currName in nameList:
            currResult = {}
            currResult['name'] = currName
            if not currName in self.procDict.keys():
                currResult['status'] = -1
                currResult['msg'] = '目标进程不存在, 请手动添加配置'
                resultList.append(currResult)
            elif self.getSpecProcStatus(currName)['status']:
                currResult['status'] = 2
                currResult['msg'] = '目标进程已启动, 未执行操作'
                resultList.append(currResult)
            else:
                start_command = self.procDict[currName]['start_command']
                try:
                    ## status = os.system(start_command)
                    subprocess.Popen(start_command, shell=True, close_fds=True)
                    currResult['status'] = 1
                    currResult['msg'] = '启动命令已执行'
                    resultList.append(currResult)
                except OSError, e:
                    currResult['status'] = -2
                    currResult['msg'] = '启动命令执行失败'
                    resultList.append(currResult)
        return resultList
    def startSpecProc(self, name):
        nameList = []
        nameList.append(name)
        self.startProc(nameList)

    def stopProc(self, nameList):
        resultList = []
        if 'all' in nameList:
            nameList = self.procDict.keys()
        for currName in nameList:
            currResult = {}
            currResult['name'] = currName
            if not currName in self.procDict.keys():
                currResult['status'] = -1
                currResult['msg'] = '目标进程不存在'
                resultList.append(currResult)
            elif  not self.getSpecProcStatus(currName)['status']:
                currResult['status'] = 2
                currResult['msg'] = '目标进程未运行, 未执行操作'
                resultList.append(currResult)
            else:
                stop_command = self.procDict[currName]['stop_command']
                try:
                    ## status = os.execve(stop_command, [stop_command], {})
                    ## status = os.system(stop_command)
                    subprocess.Popen(stop_command, shell=True, close_fds=True)
                    currResult['status'] = 1
                    currResult['msg'] = '停止命令执行完成'
                    resultList.append(currResult)
                except OSError, e:
                    currResult['status'] = -2
                    currResult['msg'] = '停止失败'
                    resultList.append(currResult)
        return resultList

    def coolDownProc(self, nameList):
        '''
        function: 将目标进程加入到冷却列表中, 在这个列表中的进程停掉的时候pyadmin服务不会尝试重启它. 适合业务升级时使用.
        '''
        resultList = []
        if 'all' in nameList:
            nameList = self.procDict.keys()
        for currName in nameList:
            currResult = {}
            currResult['name'] = currName
            if not currName in self.procDict.keys():
                currResult['status'] = -1
                currResult['msg'] = '目标进程不存在'
                resultList.append(currResult)
            else:
                currResult['status'] = self.getSpecProcStatus(currName)['status']
                print('currResult %s' % currResult['status'])
                currResult['msg'] = '冷却中'
                resultList.append(currResult)
                if not currName in self.cdList: 
                    self.cdList.append(currName)
        return resultList

    def coolDownSpecProc(self, name):
        pass
    def meltUpProc(self, nameList):
        resultList = []
        if 'all' in nameList:
            nameList = self.procDict.keys()
        for currName in nameList:
            currResult = {}
            currResult['name'] = currName
            if not currName in self.procDict.keys():
                currResult['status'] = -1
                currResult['msg'] = '目标进程不存在'
                resultList.append(currResult)
            else:
                currResult['status'] = self.getSpecProcStatus(currName)['status']
                currResult['msg'] = '无冷却'
                resultList.append(currResult)
                if currName in self.cdList:
                    self.cdList.remove(currName)
        return resultList

    def meltUpSpecProc(self, name):
        pass

    def getCdListStr(self):
        return ','.join(self.cdList)

    def killProc(self, nameList):
        '''
        对目标进程执行kill -9操作, 需要特别谨慎(有些进程的子进程会变成孤儿进程, 不好处理).
        '''
        resultList = []
        if 'all' in nameList:
            nameList = self.procDict.keys()
        for currName in nameList:
            currResult = {}
            currResult['name'] = currName
            if not currName in self.procDict.keys():
                currResult['status'] = -1
                currResult['msg'] = '目标进程不存在'
                resultList.append(currResult)
            else:
                ## try:
                ##     for i in self.procStatusList:
                ##         if i['name'] == currName:
                ##             for pid in i['pid']:
                ##                 os.kill(int(pid), signal.SIGKILL)
                ##             currResult['status'] = 1
                ##             currResult['msg'] = 'kill命令执行完成'
                ##             resultList.append(currResult)
                ##             break
                ## except OSError, e:
                ##     currResult['status'] = -2
                ##     currResult['msg'] = 'kill命令执行失败'
                ##     resultList.append(currResult)

                for i in self.procStatusList:
                    if i['name'] == currName:
                        for pid in i['pid']:
                            try:
                                os.kill(int(pid), signal.SIGKILL)
                            except Exception, e:
                                print(e)
                        currResult['status'] = 1
                        currResult['msg'] = 'kill命令执行完成'
                        resultList.append(currResult)
                        break
        return resultList

    def reloadCfg(self, cfg_opts):
        self.procDict, self.groupDict = cfg_opts